---
title: 'Implementing Federation, Part I'
url: /2023/12/implementing-federation-i/
draft: false
categories:
- en
date: 2023-12-15T14:04:04+0100
tags:
- ActivityPub
- Federation
- HTTP
- NLnet
- RFC7033
- Seppo
- WebFinger
type: post
author: Marcus Rohrmoser
---

Currently I am implementing communication (a.k.a. federation) with ActivityPub-
speaking servers for #Seppo![^seppo], generously funded by an NLnet grant[^grant].
This is Part I of a casual article series 'Implementing [Federation](/tags/federation/)`
to collect my findings.

This is nothing for the faint-hearted, it comes with both technical and social
obstacles. Ironic for a thing coined 'Social Web'.

## Obstacles

1. if you implement 'vanilla' ActivityPub[^AP], no server I know of shall
   respond
2. the canonical test-suite is down from day 1
3. some projects don't provide no servers you are allowed to develop against
4. most servers prefer to not tell you what's wrong with how you did it
5. implementers are often unable to cooperate on peer questions
6. if things don't work, few operators will investigate and tell you why
7. almost all servers use complex tech stacks with many moving parts and
   databases and require root access. This makes setting up private test servers
   a time-consuming, ongoing endeavour
8. you have to add random things you didn't hear about through the standard but
   by word-of-mouth
9. your development server has to be reachable via https

While some colleagues run their projects like concept art (which is fine), the
whole topic is getting broader public attention. I think there should be ready-to-use,
sovereignty-friendly offerings for the wider public. #Seppo! goes exactly
there, as it can be deployed to commodity shared webspace as offered by numerous
vendors[^vario] in the EU.

The whole topic urgently needs more social touch and friendly cooperation.

So I decided to write down what works, up to the point where you can post,
subscribe and be subscribed.

## Wording

I usually prefer to stick with canonical wording, but found "following" and
"followers" a bit like the "People's Front of Judea" vs. "Judean People's Front"[^brian]
-- even after months of usage. So I will rather go with "subscribe" and "notify"
instead. Besides, it follows Don Normans recommendation to either differ
strongly or not at all.

# Discovery (WebFinger)

In order to resolve adresses like `@alice@example.org` your server must
implement the webfinger protocol RFC7033[^rfc7033].

The lookup starts on the target server with a 'well-known uri'[^rfc7033_well] blessed in the according
IANA registry[^iana]. There are some projects that want
to lookup the well known, but this is without practical benefit and beyond RFC7033.
To avoid a hardcoded `.well-known/webfinger` url they prefer a hardcoded
`.well-known/host-meta` and parsing two documents instead of one. According to
RFC6415[^rfc6415], the `host-meta` may be xml, json being optional (sic!).

So let's start with webfinger:

```sh
$ curl -L 'https://example.org/.well-known/webfinger?resource=acct:alice@example.org'
```

Some servers pretend to not know which type you want (despite the fact they
won't return anything but json), so let's add the according
header[^rfc7033_type] and make them happy:

```sh
$ curl -L -H 'Accept: application/jrd+json' 'https://example.org/.well-known/webfinger?resource=acct:alice@example.org'
```

which should yield something including

```json
{
  "subject": "acct:alice@example.org",
  "links": [
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://example.org/users/alice"
    }
  ]
}
```

Usually there's more, but the `href` from the `self` link with type
`application/activity+json` is all we need to begin with.

Nota bene, this isn't ActivityPub. However, many servers require it.

# The Actor Profile document

With above `self` url and a juicy accept header[^AP_mime] \(not the
`type` from the webfinger response above and again, many servers insist on it),
we get the description of one single actor[^AP_actor]

```sh
$ curl -L \
  -H 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"' \
  'https://example.org/users/alice'
"@context": [ ... ]
  "id": "https://example.org/users/alice",
  "type": "Person",
  "following": "https://example.org/users/alice/following",
  "followers": "https://example.org/users/alice/followers",
  "inbox": "https://example.org/users/alice/inbox",
  "outbox": "https://example.org/users/alice/outbox",
  "preferredUsername": "alice",
  "publicKey": {
     "id": "https://example.org/users/alice#main-key",
     "owner": "https://example.org/users/alice", /* deprecated https://w3c-ccg.github.io/security-vocab/#owner but mandatory for mastodon */
     "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3\n6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6\nZ4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw\noYi+1hqp1fIekaxsyQIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  "published": "2022-03-31T00:00:00Z",
  "summary": "<p>Bui ... </p>",
  ...
}
```

Besides the mandatory `id`, `type`, `inbox` and `outbox`, you definitively need

- `preferredUsername`
- `publicKey`

`preferredUsername` has to be the local part of the webfinger address above.
`publicKey` is needed for request signing below.

Again, in contrast to the standard. [@mike@macgirvin.com](https://macgirvin.com/channel/mike)
posted some useful remarks on Mar 27th 2023 exclusively to his
subscribers: https://macgirvin.com/item/f291a9e5-18be-436f-83f1-388676004784 / 
https://digitalcourage.social/@mike@macgirvin.com/110092446109049846

# Request signing

Again, not defined in the ActivityPub standard. "B.1 Authentication and
Authorization"[^AP_auth] refers to "SocialCG/ActivityPub/Authentication
Authorization"[^http_sign] which in turn points to "Signing HTTP
Messages"[^cavage08]. It has an example how such a `Signature`
header has to look like and how to verify. Once I made a working
example with `sh` and `openssl`[^http_sign_openssl].

Mastodon insists on signatures and many others do as well. Some servers won't
even give you an actor document (i.e. the `publicKey`) without.

The `publicKey.id` url has to resolve to the actor profile document as of [^http_sign].

You won't federate without.

https://docs.joinmastodon.org/spec/security/#http refers to
https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures [^cavage] which says

> It is inappropriate to use Internet-Drafts as reference material or to cite them
> other than as "work in progress."
> 
> This Internet-Draft will expire on April 22, 2020.

I leave this without further comment.

# Optional fields or empty Array?

E.g. the `inReplyTo`[^AS_inreplyto] field is optional. E.g. Mastodon silently
discards messages with the field present but an empty array as value.

Above standard says "Indicates one or more entities for which this object is
considered a response."

Sigh. Not zero. How fussy can you be?

# Being subscribed to

t.b.d. in a later article about 'Implementing [Federation](/tags/federation/)'..

# References

[^10]:           rumour goes, there are even servers that insist on other (wrong) content-type
[^AP_actor]:     4.1 Actor objects https://w3.org/TR/activitypub/#actor-objects
[^AP_auth]:      B.1 Authentication and Authorization https://www.w3.org/TR/activitypub/#authorization
[^AP_mime]:      3.2 Retrieving objects https://www.w3.org/TR/activitypub/#retrieving-objects
[^AP]:           ActivityPub https://www.w3.org/TR/activitypub/
[^AS_inreplyto]: inReplyTo https://w3.org/TR/activitystreams-vocabulary/#dfn-inreplyto
[^brian]:        Judean People's Front https://www.youtube.com/embed/a0BpfwazhUA
[^cavage]:       Signing HTTP Messages https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures
[^cavage08]:     Signing HTTP Messages / 4.1.1 RSA Example https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-08#autoid-21
[^grant]:        https://NLnet.nl/project/Seppo
[^http_sign_openssl]: https://codeberg.org/mro/activitypub/src/commit/4b1319d5363f4a836f23c784ef780b81bc674013/like.sh#L118
[^http_sign]:    https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization#Signing_requests_using_HTTP_Signatures
[^iana]:         Well-Known URIs https://iana.org/assignments/well-known-uris/well-known-uris.xhtml
[^rfc6415]:      Web Host Metadata https://rfc-editor.org/rfc/rfc6415
[^rfc7033_type]: 10.2. JSON Resource Descriptor (JRD) Media Type https://rfc-editor.org/rfc/rfc7033.html#section-10.2
[^rfc7033_well]: 10.1. Well-Known URI https://www.rfc-editor.org/rfc/rfc7033#section-10.1
[^rfc7033]:      WebFinger https://rfc-editor.org/rfc/rfc7033
[^seppo]:        https://Seppo.Social
[^vario]:        e.g. https://www.variomedia.de/hosting/easy/
